using System;
using System.Drawing;
using System.Collections;
using System.Windows.Forms;
using System.ComponentModel;
using Microsoft.Win32;
using needle.UI.Controls.Flat.Windows.Forms;
using needle.UI.Controls.Flat.Windows.Forms.Collections;
using needle.UI.Controls.Flat.Windows.Forms.Helpers;

namespace needle.UI.Controls.Flat.Windows.Docking
{
    [ToolboxItem(false)]
    public class TabStub : UserControl
    {
		#region Nested types
		
		private class DrawTab
		{
			protected int mindex;
			protected Rectangle mdrawRect;
            protected needle.UI.Controls.Flat.Windows.Forms.TabPage mtabPage;

			public DrawTab(needle.UI.Controls.Flat.Windows.Forms.TabPage tabPage, Rectangle drawRect, int index)
			{
				mindex = index;
				mtabPage = tabPage;
				mdrawRect = drawRect;
			}

			public needle.UI.Controls.Flat.Windows.Forms.TabPage TabPage  { get { return mtabPage; } }
            public Rectangle DrawRect                        { get { return mdrawRect; } }
			public int Index                                 { get { return mindex; } }
		}

		#endregion

		#region TabStubIndexEventArgs

		public class TabStubIndexEventArgs : EventArgs
		{

			int pageIndex;

			public TabStubIndexEventArgs(int pageIndex)
			{
				this.pageIndex = pageIndex;
			}

			public int PageIndex
			{
				get {return this.pageIndex;}
			}
		}

		#endregion
		
		protected static int mimageGap = 3;
		protected static int mimageGaps = 6;
        protected static int mimageVector = 16;
        protected static int mbeginGap = 2;
        protected static int mendGap = 8;
        protected static int msideGap = 2;
		protected static int mhoverInterval = 500;

		protected Edge medge;
		protected int mhoverOver;
		protected int mhoverItem;
		protected int mselectedIndex;
    	protected bool mdefaultFont;
		protected bool mdefaultColor;
		protected Color mbackIde;
        protected Timer mhoverTimer;
        protected TabPageCollection mtabPages;
		protected WindowContentTabbed mwct;
		protected ArrayList mdrawTabs;

        public delegate void TabStubIndexEventHandler(object sender, TabStubIndexEventArgs e);
        public delegate void TabStubEventHandler(object sender, EventArgs e);

        public event TabStubIndexEventHandler PageClicked;
        public event TabStubIndexEventHandler PageOver;
        public event TabStubEventHandler PagesLeave;

		#region Constructors

		public TabStub()
		{
			mwct = null;
            mhoverOver = -1;
            mhoverItem = -1;
            mselectedIndex = -1;
            mdefaultFont = true;
			mdefaultColor = true;
			medge = Edge.None;
			mdrawTabs = new ArrayList();
            mtabPages = new TabPageCollection();
            base.Font = SystemInformation.MenuFont;

            mtabPages.Cleared += new CollectionClearEventHandler(OnClearedPages);
            mtabPages.Inserted += new needle.UI.Controls.Flat.Windows.Forms.Collections.CollectionChangeEventHandler(OnInsertedPage);
            mtabPages.Removing += new needle.UI.Controls.Flat.Windows.Forms.Collections.CollectionChangeEventHandler(OnRemovingPage);
            mtabPages.Removed += new needle.UI.Controls.Flat.Windows.Forms.Collections.CollectionChangeEventHandler(OnRemovedPage);

            Microsoft.Win32.SystemEvents.UserPreferenceChanged += new 
                UserPreferenceChangedEventHandler(OnPreferenceChanged);

			DefineBackColor(SystemColors.Control);

			mhoverTimer = new Timer();
			mhoverTimer.Interval = mhoverInterval;
			mhoverTimer.Tick += new EventHandler(OnTimerExpire);
		}

		#endregion

		#region Dispose

		protected override void Dispose(bool disposing)
        {
            if(disposing)
            {
                Microsoft.Win32.SystemEvents.UserPreferenceChanged -= new 
                    UserPreferenceChangedEventHandler(OnPreferenceChanged);
            }
            base.Dispose(disposing);
        }

		#endregion

		#region Virtuals

		protected void OnTimerExpire(object sender, EventArgs e)
		{
			mhoverTimer.Stop();

			if (mhoverItem != mhoverOver)
			{
				mhoverItem = mhoverOver;
                
				mhoverOver = -1;

				if (mselectedIndex != mhoverItem)
				{
					mselectedIndex = mhoverItem;

					Recalculate();
					Invalidate();
				}

				OnPageOver(mselectedIndex);
			}
		}


		public virtual void OnPageClicked(int pageIndex)
		{
			if (PageClicked != null)
				PageClicked(this, new TabStubIndexEventArgs(pageIndex));
		}

		public virtual void OnPageOver(int pageIndex)
		{
			if (PageOver != null)
				PageOver(this, new TabStubIndexEventArgs(pageIndex));
		}

        public virtual void OnPagesLeave()
        {
            if (PagesLeave != null)
                PagesLeave(this, EventArgs.Empty);
        }

		protected void DefineBackColor(Color backColor)
		{
			base.BackColor = backColor;
			
			mbackIde = ColorUtilities.TabBackgroundFromBaseColor(backColor);
		}

		protected void OnPreferenceChanged(object sender, UserPreferenceChangedEventArgs e)
		{
			if (mdefaultFont)
			{
				base.Font = SystemInformation.MenuFont;
				ResizeControl();
				Recalculate();
				Invalidate();
			}
		}

		protected void OnClearedPages(object sender, EventArgs e)
		{
			CancelHoverItem();

			mselectedIndex = -1;

			ResizeControl();
			Recalculate();
			Invalidate();
		}
		
		protected void OnInsertedPage(object sender, needle.UI.Controls.Flat.Windows.Forms.Collections.CollectionChangeEventArgs e)
		{
			if (mselectedIndex == -1)
			{
				mselectedIndex = e.Index;
			}

			needle.UI.Controls.Flat.Windows.Forms.TabPage page = sender as needle.UI.Controls.Flat.Windows.Forms.TabPage;

			Content c = page.Tag as Content;

			c.PropertyChanged += new Content.PropChangeEventHandler(OnContentChanged);

			ResizeControl();
			Recalculate();
			Invalidate();
		}

		protected void OnRemovingPage(object sender, needle.UI.Controls.Flat.Windows.Forms.Collections.CollectionChangeEventArgs e)
		{
			if ((mhoverOver == e.Index) || (mhoverItem == e.Index))
				CancelHoverItem();
        
			if (mtabPages.Count == 1)
			{
				mselectedIndex = -1;
			}
			else
			{
				if (e.Index < mselectedIndex)
				{
					mselectedIndex--;
				}
				else
				{
					if (mselectedIndex == (mtabPages.Count-1))
					{
						mselectedIndex--;
					}
				}
			}
		}

		protected void OnRemovedPage(object sender, needle.UI.Controls.Flat.Windows.Forms.Collections.CollectionChangeEventArgs e)
		{
			needle.UI.Controls.Flat.Windows.Forms.TabPage page = sender as needle.UI.Controls.Flat.Windows.Forms.TabPage;

			Content c = page.Tag as Content;

			c.PropertyChanged -= new Content.PropChangeEventHandler(OnContentChanged);

			ResizeControl();
			Recalculate();
			Invalidate();
		}

		protected void OnContentChanged(object sender, Content.PropChangeEventArgs e)
		{
			bool update = false;

			foreach(needle.UI.Controls.Flat.Windows.Forms.TabPage page in mtabPages)
			{
				if (page.Tag == sender)
				{
					switch(e.PropertyName)
					{
						case "Title":
							page.Title = ((Content)sender).Title;
							update = true;
							break;
						case "ImageList":
							page.ImageList= ((Content)sender).ImageList;
							update = true;
							break;
						case "ImageIndex":
							page.ImageIndex= ((Content)sender).ImageIndex;
							update = true;
							break;
						case "Icon":
							page.Icon= ((Content)sender).Icon;
							update = true;
							break;
					}
				}

				if (update)
					break;
			}

			if (update)
			{
				ResizeControl();
				Recalculate();
				Invalidate();
			}
		}

		protected void CancelHoverItem()
		{
			if (mhoverOver != -1)
			{
				mhoverTimer.Stop();
                
				mhoverOver = -1;
			}

			if (mhoverItem != -1)
			{
				mhoverItem = -1;
		        
				OnPagesLeave();
			}
		}

		protected void ResizeControl()
		{
			int textMax = 0;

			using(Graphics g = this.CreateGraphics())
			{
				foreach(needle.UI.Controls.Flat.Windows.Forms.TabPage page in mtabPages)
				{
					SizeF dimension = g.MeasureString(page.Title, this.Font);

					if ((int)dimension.Width > textMax)
						textMax = (int)dimension.Width;
				}
			}

			int variableVector = mtabPages.Count * (mimageVector + mimageGaps) + textMax + mimageGap;

			int fixedVector = TabStubVector(this.Font);

			switch(medge)
			{
				case Edge.Left:
				case Edge.Right:
					this.Size = new Size(fixedVector, variableVector + mbeginGap + mendGap);
					break;
				case Edge.Top:
				case Edge.Bottom:
				case Edge.None:
				default:
					this.Size = new Size(variableVector + mbeginGap + mendGap, fixedVector);
					break;
			}
		}

		protected void Recalculate()
		{
			mdrawTabs = new ArrayList();

			int posEnd;
			int cellVector = mimageVector + mimageGaps;
			int posStart = mbeginGap;

			switch(medge)
			{
				case Edge.Left:
				case Edge.Right:
					posEnd = this.Height - mendGap;
					break;
				case Edge.Top:
				case Edge.Bottom:
				case Edge.None:
				default:
					posEnd = this.Width - mendGap;
					break;
			}

			int count = mtabPages.Count;

			for(int index=0; (index<count) && (index!=mselectedIndex); index++)
			{
				Rectangle drawRect;

				switch(medge)
				{
					case Edge.Left:
						drawRect = new Rectangle(0, posStart, this.Width - msideGap - 1, cellVector);
						break;
					case Edge.Right:
						drawRect = new Rectangle(msideGap, posStart, this.Width - msideGap, cellVector);
						break;
					case Edge.Bottom:
						drawRect = new Rectangle(posStart, msideGap, cellVector, this.Height - msideGap);
						break;
					case Edge.Top:
					case Edge.None:
					default:
						drawRect = new Rectangle(posStart, 0, cellVector, this.Height - msideGap - 1);
						break;
				}

				posStart += cellVector;

				mdrawTabs.Add(new DrawTab(mtabPages[index], drawRect, index));
			}

			for(int index=count-1; (index>=0) && (index!=mselectedIndex); index--)
			{
				Rectangle drawRect;

				switch(medge)
				{
					case Edge.Left:
						drawRect = new Rectangle(0, posEnd - cellVector, this.Width - msideGap - 1, cellVector);
						break;
					case Edge.Right:
						drawRect = new Rectangle(msideGap, posEnd - cellVector, this.Width - msideGap, cellVector);
						break;
					case Edge.Bottom:
						drawRect = new Rectangle(posEnd - cellVector, msideGap, cellVector, this.Height - msideGap);
						break;
					case Edge.Top:
					case Edge.None:
					default:
						drawRect = new Rectangle(posEnd - cellVector, 0, cellVector, this.Height - msideGap - 1);
						break;
				}

				posEnd -= cellVector;

				mdrawTabs.Add(new DrawTab(mtabPages[index], drawRect, index));
			}

			if (mselectedIndex != -1)
			{
				Rectangle drawRect;

				switch(medge)
				{
					case Edge.Left:
						drawRect = new Rectangle(0, posStart, this.Width - msideGap - 1, posEnd - posStart);
						break;
					case Edge.Right:
						drawRect = new Rectangle(msideGap, posStart, this.Width - msideGap, posEnd - posStart);
						break;
					case Edge.Bottom:
						drawRect = new Rectangle(posStart, msideGap, posEnd - posStart, this.Height - msideGap);
						break;
					case Edge.Top:
					case Edge.None:
					default:
						drawRect = new Rectangle(posStart, 0, posEnd - posStart, this.Height - msideGap - 1);
						break;
				}

				mdrawTabs.Add(new DrawTab(mtabPages[mselectedIndex], drawRect, mselectedIndex));
			}
		}

		protected void AdjustRectForEdge(ref Rectangle rect)
		{
			switch(medge)
			{
				case Edge.Left:
					rect.X--;
					rect.Width++;
					break;
				case Edge.Right:
					rect.Width++;
					break;
				case Edge.Bottom:
					rect.Height++;
					break;
				case Edge.Top:
				case Edge.None:
				default:
					rect.Y--;
					rect.Height++;
					break;
			}
		}

		protected void DrawOutline(Graphics g, bool pre)
		{
			Rectangle borderRect = new Rectangle(0, 0, this.Width - 1, this.Height - 1);

			switch(medge)
			{
				case Edge.Left:
					borderRect.Y += mbeginGap;
					borderRect.Height -= mbeginGap + mendGap - 1;
					borderRect.Width -= msideGap;
					break;
				case Edge.Right:
					borderRect.Y += mbeginGap;
					borderRect.Height -= mbeginGap + mendGap - 1;
					borderRect.X += msideGap;
					borderRect.Width -= msideGap;
					break;
				case Edge.Bottom:
					borderRect.Y += msideGap;
					borderRect.Height -= msideGap;
					borderRect.X += mbeginGap;
					borderRect.Width -= mbeginGap + mendGap - 1;
					break;
				case Edge.Top:
				case Edge.None:
				default:
					borderRect.Height -= msideGap;
					borderRect.X += mbeginGap;
					borderRect.Width -= mbeginGap + mendGap - 1;
					break;
			}

			AdjustRectForEdge(ref borderRect);

			if (pre)
			{
				using(SolidBrush fillBrush = new SolidBrush(this.BackColor))
					g.FillRectangle(fillBrush, borderRect);
			}
		}

		protected void DrawOutlineForCell(Graphics g, Pen pen, Rectangle rect)
		{
			switch(medge)
			{
				case Edge.Left:
				case Edge.Right:
					g.DrawLine(pen, rect.Left + 1, rect.Bottom, rect.Right, rect.Bottom);       
					break;                    
				case Edge.Top:
				case Edge.Bottom:
					g.DrawLine(pen, rect.Right, rect.Top + 1, rect.Right, rect.Bottom);       
					break;                    
			}
		}

		#endregion 

		#region Overrides

		protected override void OnSystemColorsChanged(EventArgs e)
		{
			if (mdefaultColor)
			{
				this.BackColor = SystemColors.Control;
				Invalidate();
			}

			base.OnSystemColorsChanged(e);
		}

		protected override void OnMouseMove(MouseEventArgs e)
		{
			Point mousePos = new Point(e.X, e.Y);

			int index = 0;
			int count = mdrawTabs.Count;

			for(; index<count; index++)
			{
				DrawTab dt = mdrawTabs[index] as DrawTab;

				if (dt.DrawRect.Contains(mousePos))
				{
					if (mhoverItem != dt.Index)
					{
						if (mhoverOver != dt.Index)
						{
							mhoverTimer.Start();
						    
							mhoverOver = dt.Index;
						}
					}

					break;
				}
			}

			if (index == count)
			{
				if ((mhoverOver != -1) || (mhoverItem != -1))
				{
					CancelHoverItem();
				}
			}

			base.OnMouseMove(e);
		}

		protected override void OnMouseLeave(EventArgs e)
		{
			CancelHoverItem();
			base.OnMouseLeave(e);
		}

		protected override void OnMouseDown(MouseEventArgs e)
		{
			if (e.Button == MouseButtons.Left)
			{
				Point mousePos = new Point(e.X, e.Y);

				int count = mdrawTabs.Count;

				for(int index=0; index<count; index++)
				{
					DrawTab dt = mdrawTabs[index] as DrawTab;

					if (dt.DrawRect.Contains(mousePos))
					{
						mhoverTimer.Stop();
						    
						mhoverItem = mselectedIndex;
						    
						mhoverOver = mhoverItem;
                        
						if (mselectedIndex != dt.Index)
						{
							mselectedIndex = dt.Index;
						
							Recalculate();
							Invalidate();
						}

						OnPageClicked(mselectedIndex);

						break;
					}
				}
			}
		}

		protected override void OnPaintBackground(PaintEventArgs e)
		{
		}

		protected override void OnPaint(PaintEventArgs e)
		{
			using(SolidBrush fillBrush = new SolidBrush(this.BackColor))
				e.Graphics.FillRectangle(fillBrush, this.ClientRectangle);

			DrawOutline(e.Graphics, true);

			using(Pen borderPen = new Pen(ControlPaint.LightLight(this.ForeColor)))
			{
				foreach(DrawTab dt in mdrawTabs)
				{
					Rectangle drawRect = dt.DrawRect;

					AdjustRectForEdge(ref drawRect);

					DrawOutlineForCell(e.Graphics, borderPen, drawRect);

					needle.UI.Controls.Flat.Windows.Forms.TabPage page = dt.TabPage;

					int xDraw;
					int yDraw;

					switch(medge)
					{
						case Edge.Left:
						case Edge.Right:
							xDraw = drawRect.Left + (drawRect.Width - mimageVector) / 2;
							yDraw = drawRect.Top + mimageGap;
							break;
						case Edge.Top:
						case Edge.Bottom:
						case Edge.None:
						default:
							xDraw = drawRect.Left + mimageGap;
							yDraw = drawRect.Top + (drawRect.Height - mimageVector) / 2;
							break;
					}

					if ((page.Icon != null) || ((page.ImageIndex != -1) && (page.ImageList != null)))
					{
						if (page.Icon != null)
						{
							e.Graphics.DrawIcon(page.Icon, new Rectangle(xDraw, yDraw, mimageVector, mimageVector));
						}
						else
						{
							e.Graphics.DrawImage(page.ImageList.Images[page.ImageIndex],
								new Rectangle(xDraw, yDraw, mimageVector, mimageVector));
						}
					}

					if (mselectedIndex != -1)
					{
						
						if (page == mtabPages[mselectedIndex])
						{
							Rectangle textRect;

							StringFormat drawFormat = new StringFormat();
							drawFormat.FormatFlags = StringFormatFlags.NoClip | StringFormatFlags.NoWrap;
							drawFormat.Alignment = StringAlignment.Center;
							drawFormat.LineAlignment = StringAlignment.Center;

							switch(medge)
							{
								case Edge.Left:
								case Edge.Right:
									textRect = new Rectangle(drawRect.Left, yDraw + mimageVector + mimageGap, 
										drawRect.Width, drawRect.Height - mimageVector - mimageGap * 2);
									drawFormat.FormatFlags |= StringFormatFlags.DirectionVertical;
									break;
								case Edge.Top:
								case Edge.Bottom:
								case Edge.None:
								default:
									textRect = new Rectangle(xDraw + mimageVector + mimageGap, drawRect.Top, 
										drawRect.Width - mimageVector - mimageGap * 2, drawRect.Height);
									break;
							}
							
							Color brushColor = this.ForeColor;
							
							using(SolidBrush drawBrush = new SolidBrush(brushColor))
								e.Graphics.DrawString(page.Title, this.Font, drawBrush, textRect, drawFormat);
						}
					}
				}
			}

			DrawOutline(e.Graphics, false);
            
			base.OnPaint(e);
		}

		#endregion

		#region Methods
        
        public void PropogateNameValue(PropogateName name, object value)
        {
            switch(name)
            {
                case PropogateName.BackColor:
                    this.BackColor = (Color)value;
                    Invalidate();
                    break;
                case PropogateName.InactiveTextColor:
                    this.ForeColor = (Color)value;
                    Invalidate();
                    break;
                case PropogateName.CaptionFont:
                    this.Font = (Font)value;
                    break;
            }
            
            mwct.PropogateNameValue(name, value);
        }

        public static int TabStubVector(Font font)
        {
            int fixedVector = mimageVector + mimageGaps;

            int minFontVector = font.Height + mimageGaps;

            if (fixedVector < minFontVector)
                fixedVector = minFontVector;
                
            return fixedVector + msideGap;
        }

		#endregion

		#region Properties

		public TabPageCollection TabPages
		{
			get { return mtabPages; }

			/*set
			{
				mtabPages.Clear();
				mtabPages = value;
			}*/
		}

		public Edge Edging
		{
			get { return medge; }

			set
			{
				if (value != medge)
				{
					medge = value;
					ResizeControl();
					Recalculate();
					Invalidate();
				}
			}
		}

		public int SelectedIndex
		{
			get { return mselectedIndex; }

			set
			{
				if (value != mselectedIndex)
				{
					mselectedIndex = value;
					Recalculate();
					Invalidate();
				}
			}
		}

		public override Font Font
		{
			get { return base.Font; }

			set
			{
				if (value != null)
				{
					if (value != base.Font)
					{
						mdefaultFont = (value == SystemInformation.MenuFont);

						base.Font = value;
						ResizeControl();
						Recalculate();
						Invalidate();
					}
				}
			}
		}

		public override Color BackColor
		{
			get { return base.BackColor; }

			set
			{
				if (this.BackColor != value)
				{
					mdefaultColor = (value == SystemColors.Control);
					DefineBackColor(value);
					Invalidate();
				}
			}
		}

		public WindowContentTabbed WindowContentTabbed
		{
			get { return mwct; }
			set { mwct = value; }
		}

		#endregion
	}
}

